﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using DataLayer.UoW;
using DataLayer.Types;
using NHibernate.Criterion;
using NHibernate.Linq;

namespace DataLayer.Repo
{
    /// <summary>
    /// A generic repository to mediate between 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class GenericRepository<T> : IRepository<T> 
        where T : class, IBaseRecord
    {

        public GenericRepository()
        {
        }

        /// <summary>
        /// Occurs when the data in the repository has changed.
        /// </summary>
        public event EventHandler<RepositoryDataChangedArgs<T>> DataChanged;

        private void OnDataChanged(RepositoryDataChangedArgs<T> args)
        {
            if(DataChanged == null) return;
            DataChanged(this, args);
        }

        public ISession Session { get { return UnitOfWork.CurrentSession; } }
        public IUnitOfWork UoW { get { return UnitOfWork.Current; } }

        #region IRepository<T> Members

        /// <summary>
        /// gets item by id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public T GetById(int id)
        {
            using (UnitOfWork.Start())
            {
                return Session.Linq<T>().FirstOrDefault(x => x.Id == id);
            }
        }

        public T GetByName(string name)
        {
            using (UnitOfWork.Start())
            {
                T item = Session
                    .CreateCriteria(typeof (T))
                    .Add(Restrictions.Eq("Name", name))
                    .UniqueResult<T>();
                return item;
            }
        }

        public void Add(T entity)
        {
            using (UnitOfWork.Start())
            {
                Session.Save(entity);
                UoW.TransactionalFlush();
            }
            OnDataChanged(new RepositoryDataChangedArgs<T>(new List<T>{entity}, DataChangeType.Added));
        }

        public void AddRange(ICollection<T> entities)
        {
            using (UnitOfWork.Start())
            {
                foreach (T entity in entities)
                {
                    Session.Save(entity);
                    UoW.TransactionalFlush();
                }
            }
            OnDataChanged(new RepositoryDataChangedArgs<T>(entities, DataChangeType.Added));
        }

        public void Update(T entity)
        {
            using (UnitOfWork.Start())
            {
                Session.Update(entity);
                UoW.TransactionalFlush();
            }
            OnDataChanged(new RepositoryDataChangedArgs<T>(new List<T> { entity }, DataChangeType.Updated));
        }

        public void Remove(T entity)
        {
            using (UnitOfWork.Start())
            {
                Session.Delete(entity);
                UoW.TransactionalFlush();
            }
            OnDataChanged(new RepositoryDataChangedArgs<T>(new List<T> { entity }, DataChangeType.Removed));
        }

        public ICollection<T> FindAll()
        {
            using (UnitOfWork.Start())
            {
                return Session.CreateCriteria(typeof(T)).List<T>();
            }
        }

        ///TODO: change to query object, not string.
        /// <summary>
        /// Generic.
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public ICollection<T> FindByQuery(string query)
        {
            using (UnitOfWork.Start())
            {
                return Session.CreateQuery(query).List<T>();
            }
        }

        #endregion

    }
}
